接模型時,很容易把成功想成一件簡單的事:送出請求,收到回覆。這次「從心出發」確實收到了 HTTP 200,也就是網路介面回報請求成功的狀態碼。但最後留下的整合結果,仍然是 BLOCKED:必要條件不足,不能放行。
卡住的地方很小,只有回應裡的模型名稱。
Day 01,我用工程日誌約束自己能宣稱什麼;這一篇,同樣的問題落到了真正的模型介面上。本篇整理接線與後續身分查核的既有紀錄,並非同一天新做完的實驗;為了寫文章,我沒有重新呼叫模型。
這次要接的是大型語言模型(LLM),負責在允許的一般支持情境中產生文字。程式已把模型供應端(provider)抽成獨立介面,再用轉接器(adapter)處理送往校方模型閘道的請求。閘道是代為轉送模型請求的服務;把它隔開,對話流程就不必自己處理每一種網路錯誤。
但「已寫好轉接器」離「已驗證接上指定模型」,還有一段距離。
最初的驗證缺少必要設定,因此沒有送出請求。後來設定到位,授權驗證送出了 1 次最小推論請求,也就是只請模型產生少量測試文字,不送真實使用者對話。
那次回覆是 HTTP 200,JSON 也可解析。JSON 是介面交換結構化資料的文字格式;能解析,只表示程式讀得懂資料,不表示內容符合期待。報告裡的 responseModelMatches 是 false:模型名稱沒有符合要求,轉接器回報 SCHEMA_ERROR。
這份最初的紀錄沒有留下回傳名稱原字串,我不能用後來看到的值替它補答案。
後續身分查核另送了 1 次最小推論請求,才保存下列對照:
| 欄位 | 實際紀錄 |
|---|---|
| 請求的模型名稱 | gpt-5.6-luna |
| 回應中的模型名稱 | gpt-5.6-luna-2026-07-09 |
| HTTP 狀態 | 200 |
| JSON 解析 | 通過 |
| 名稱完全相同 | 否 |
| 轉接器驗收 | FAIL/SCHEMA_ERROR |
兩次推論都沒有重試。這不是同一筆回覆的不同寫法,也不是「多試幾次,總有一次會過」。
Schema 是資料必須遵守的欄位與型別規則。在目前實作中,模型身分也被放進回應契約,因此 SCHEMA_ERROR 不只可能代表少了欄位;名稱不符,同樣會被拒絕。
轉接器在解析 JSON 後,先做這個判斷:
if payload["model"] != config.model:
raise ValueError
這是現有程式的一小段。周圍的錯誤處理會把它轉為 SCHEMA_ERROR。只有名稱相同,才繼續檢查候選回覆、結束原因、訊息角色與文字內容;這次在前面就被擋住,不能宣稱完整 schema 已通過。
下面依現有程式簡化流程,省略設定、逾時與大小限制的細項,以及所有回覆共用的最後檢查。它是工作樹實作的說明,不是已部署的架構圖。
flowchart TD
A[輸入先經規則分流] --> B{允許一般生成?}
B -- 否 --> C[危機或受限分支:固定回覆]
B -- 是 --> D[向設定的 provider 送出請求]
D --> E{HTTP 與回應格式檢查通過?}
E -- 否 --> X[拒絕模型輸出,使用固定備援]
E -- 是 --> F{JSON 可解析?}
F -- 否 --> X
F -- 是 --> G{model 與設定完全相同?}
G -- 否 --> X
G -- 是 --> H{其餘回應欄位合格?}
H -- 否 --> X
H -- 是 --> I{生成文字通過安全檢查?}
I -- 否 --> X
I -- 是 --> J[保留生成文字供最後檢查]
圖裡沒有「別名自動辨識」這個步驟,因為現行轉接器沒有實作它。模型回覆通過介面契約,也還要經過輸出安全檢查,不能直接交給使用者。
看到名稱多了日期,很容易提出一個假設:這會不會只是指定版本?模型清單裡又出現 azure/gpt-5.6-luna,看起來也很接近。
但這裡有三種不同用途的名稱:送出請求用的名字、生成回應宣告的名字,以及模型清單列出的名字。它們相似,不代表已經建立對應關係。
Alias 是別名;canonical identity 則是在契約中用來明確指認模型的正式身分。要接受別名,缺的不是刪掉日期的程式,而是有權說明這個關係的來源:例如供應方文件,或經獨立審查、明確標示版本與適用範圍的閘道對照資料。
後續查核保留了更完整的模型清單,但仍沒有取得能證明等價的資料。所以我的結論是:「我沒有證據證明它們相同。」這也不等於已經證明它們是不同模型。
紀錄將它分類為 UNPROVEN_VARIANT,意思是觀察到不同名稱,等價性尚未證明。這裡核對的是閘道宣告是否符合契約,並沒有直接驗證底層實際運行的模型;即使名稱完全相同,也不能單靠字串比較證明後端的一切。
Fail-open 是條件無法確認時仍讓流程繼續;fail-closed 則是在必要條件不成立時拒絕放行。這次如果改成「名稱有共同開頭就接受」,其實就是把尚未證明的假設寫進允許條件。
目前沒有這樣改。執行時,轉接器拒絕不符契約的回應;如果一般支持流程遇到這類錯誤,就使用明確標示的固定備援文字。整合驗收則保留 BLOCKED。前者是使用者會走到的回覆路徑,後者是工程上不能宣布完成的狀態。
這件事回到「從心出發」,不只是回答品質問題。我不能假定名稱接近的模型,在能力與限制上就可以互換;否則部署時也說不清楚,之前的檢查究竟適用於哪個供應端。
心理支持還需要更早的邊界。危機分流(crisis routing)是先依規則判斷是否進入固定支持路徑,不把是否繼續生成交給模型。現有程式對危機、高度困擾與未知風險都會避開一般模型呼叫。既有查核中的 23 個人工編寫的合成危機案例,全部走固定危機支持分支,provider 呼叫、傳輸嘗試及網路呼叫都為 0;這是有限測試案例的結果,本次沒有重跑,也不能推成所有危機都能辨識。
模型身分契約不能替代安全政策,安全政策也不能替身分不明背書。「從心出發」仍是非臨床工程專案,不宣稱診斷、治療或取代專業心理師;現有規則式檢查也不是任意文字的安全保證。
最後一輪查核把「什麼證據才足以接受別名」寫成政策,並建立隔離的 QA 契約驗證器。QA 指品質驗證;這個工具用來測試契約會不會錯放行,尚未接進正式執行流程。
它有 42 個受控案例通過,另有 2 個既有轉接器模擬測試方法通過。這些正向案例使用合成來源,只能證明測試條件下的驗證行為,不能替真實模型建立別名關係。實際待解契約仍是 active=false,可接受名稱清單為空。
以下都是本篇採用的既有紀錄,沒有在寫作時重新執行。其中回歸測試,是檢查原有功能是否受到改動影響:
| 項目 | 結果 |
|---|---|
| 對話重點測試/安全測試/回歸測試 | 87/24/160 項,原報告皆 PASS;範圍重疊,不能相加 |
| 最小推論的介面回覆 | 曾收到 HTTP 200,但轉接器驗收 FAIL |
| 模型名稱等價關係 | UNKNOWN,尚無權威對照證據 |
| 最新身分查核 | 新增模型清單請求 0、推論請求 0;維持 BLOCKED |
| 別名契約 | 隔離 QA 設計存在;未啟用,未接入正式執行流程 |
最後沒有再送請求,是因為缺的證據已經變了:另一段生成文字,不能回答誰有權確認別名關係。
一般心理支持情境的真實模型驗收仍是 NOT_RUN,也就是沒有執行;不能用模擬測試通過來填補。真實生成能力仍記為未驗證,模型等價性也沒有確認。
最後一輪身分查核沒有重跑完整測試與危機測試,只沿用已保存的證據;本次寫作也沒有重跑這些測試。相關對話實作與驗證成果尚未形成新的 Git commit(版本提交),也沒有在這些工作中部署。外部測試環境與正式環境的現況,本篇未查驗。
UNPROVEN_VARIANT 比猜成「同一個」或「錯的模型」更準確。下一個候選題目,是取得並審查一份真正有來源的模型對照資料:由誰發布、適用於哪個部署、何時生效,以及能否支持已保存的那次回應。這是待做的工作,不是已拿到的文件。
即使資料到位,也還得另外審查如何接入執行流程。我要補的是可驗證的依據,才能讓下一次「成功」有明確的意思。